package com.rain.example.verifyPermissions.controller;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.rain.example.verifyPermissions.constants.Constants;
import com.rain.example.verifyPermissions.models.user.Role;
import com.rain.example.verifyPermissions.models.user.User;
import com.rain.example.verifyPermissions.models.user.UserRole;
import com.rain.example.verifyPermissions.response.Result;
import com.rain.example.verifyPermissions.service.RoleService;
import com.rain.example.verifyPermissions.service.UserRoleService;
import com.rain.example.verifyPermissions.service.UserService;
import com.rain.example.verifyPermissions.utils.JWTTokenUtil;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/login")
public class LoginController {
    private static final Logger log = LoggerFactory.getLogger(LoginController.class);
    @Value("${sms.token.salt}")
    private static String smsSalt;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Resource
    private UserService userService;
    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private RoleService roleService;

    /**
     * 注册
     *
     * @return
     */
    @PostMapping("/register")
    @Transactional
    public Result register(@RequestParam String username, @RequestParam String password) {
        //先判断是否用户名已存在
        List<User> users = userService.list(Wrappers.<User>lambdaQuery().eq(User::getLoginName, username));
        if (CollectionUtils.isNotEmpty(users)) {
            return Result.error("该用户名已被注册");
        }
        // 注册用户
        User user = new User();
        user.setLoginName(username);
        user.setPassword(encodePassword(password));
        // 设置用户状态
        user.setDeleteFlag(0);
        user.setType(Constants.INT_ZERO);
        user.setAddTime(new Date());
        userService.save(user);
        // 分配角色 1:ADMIN 2:USER
        Role role = roleService.getOne(Wrappers.<Role>lambdaQuery().eq(Role::getRoleName, "admin").select(Role::getRoleId, Role::getRoleName));
        UserRole userRole = new UserRole();
        userRole.setRoleId(role.getRoleId());
        userRole.setRoleName(role.getRoleName());
        userRole.setUserId(user.getUserId());
        userRole.setDeleteFlag(Constants.INT_ZERO);
        userRoleService.save(userRole);
        return Result.success("注册成功");
    }

    /**
     * 通过电话号码登录。
     * 使用电话号码和短信验证码进行登录验证，如果用户不存在，则创建新用户。
     * 验证码通过Redis进行存储和校验，确保验证码的有效性。
     * 使用JWT生成访问令牌，用于用户身份验证。
     *
     * @param phone 用户的电话号码，作为登录名。
     * @param code  用户输入的短信验证码。
     * @return 如果登录成功，返回包含JWT令牌的成功响应；否则返回错误信息。
     */
    @PostMapping("/loginByPhone")
    public Result loginByPhone(@RequestParam String phone, @RequestParam String code) {
        try {
            // 从Redis中获取存储的验证码
            String smsToken = redisTemplate.opsForValue().get("smsToken_" + phone);
            // 验证验证码是否过期
            if (StringUtils.isEmpty(smsToken)) {
                return Result.error("验证码已过期，请重新获取");
            }
            // 生成基于验证码的短信令牌进行校验
            String generatedSmsToken = generateSmsToken(code);
            // 验证用户输入的短信令牌是否正确
            if (!generatedSmsToken.equals(smsToken)) {
                return Result.error("验证码错误，请重试");
            } else {
                // 查询是否已存在该电话号码的用户
                // 先判断是否是第一次登录,如果是第一次登录，先创建用户，不是第一次登录，则直接生成token返回即可
                User user = userService.getOne(Wrappers.<User>lambdaQuery().eq(User::getLoginName, phone).eq(User::getDeleteFlag, Constants.INT_ZERO));
                // 如果用户不存在，则创建新用户
                if (ObjectUtils.isEmpty(user)) {
                    user = new User();
                    user.setLoginName(phone);
                    // 设置初始密码
                    String password = encodePassword(Constants.login_origin_password);
                    user.setPassword(password);
                    user.setAddTime(new Date());
                    user.setType(Constants.INT_ONE);
                    user.setDeleteFlag(Constants.INT_ZERO);
                    userService.save(user);
                }
                // 为用户生成JWT令牌
                String token = JWTTokenUtil.createAccessToken(user);
                // 验证码使用完毕后，从Redis中删除
                if (Boolean.TRUE.equals(redisTemplate.hasKey("smsToken_" + phone))) {
                    redisTemplate.delete("smsToken_" + phone);
                }
                // 返回成功响应，包含JWT令牌
                return Result.success(token);
            }
        } catch (Exception ignored) {
            return Result.error("登录失败");
        }
    }

    /**
     * 通过POST请求生成并发送手机验证码。
     *
     * @param phone 用户的电话号码。
     * @return 成功生成验证码后，返回包含验证码token的结果。
     * <p>
     * 此方法首先生成一个随机的四位数验证码，然后将其存储在Redis中，
     * 设置有效时间为两分钟。接着，生成一个基于该验证码的token，
     * 用于后续的验证码验证过程，最后将token作为结果返回。
     */
    @PostMapping("/getPhoneCAPTCHA")
    public Result getPhoneCAPTCHA(@RequestParam String phone) {
        //模拟验证码发送到手机，并存储进redis中作为结果返回
//        String code = "123456";
        //此处模拟生成验证码
        String code = String.valueOf(RandomUtils.nextInt(1000, 9999));
        log.info("短信验证码为:{}", code);
        String token = generateSmsToken(code);
        //存储进redis，有效时间两分钟
        redisTemplate.opsForValue().set("smsToken_" + phone, token, Constants.INT_TWO, TimeUnit.MINUTES);
        return Result.success("短信验证码已发送至您的手机");
    }

    /**
     * 登录
     *
     * @param username 用户名
     * @param password 密码
     * @return Result
     */
    @PostMapping("userLogin")
    public Result userLogin(@RequestParam String username, @RequestParam String password) {
        User user = userService.getOne(Wrappers.<User>lambdaQuery().eq(User::getLoginName, username));
        if (ObjectUtils.isEmpty(user)) {
            return Result.error("用户名或密码错误，请重试");
        }
        String password1 = user.getPassword();
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        if (!encoder.matches(password, password1)) {
            return Result.error("密码错误");
        }
        return Result.success(user);
    }

    /**
     * 使用SHA-256生成基于短信验证码的Token
     *
     * @param smsCode 短信验证码
     * @return SHA-256哈希值作为Token
     */
    public static String generateSmsToken(String smsCode) {
        try {
            //从配置中心nacos或者yaml中获取加密盐
            String salt = smsSalt;
            // 创建一个MessageDigest实例，初始化为SHA-256算法
            MessageDigest digest = MessageDigest.getInstance("SHA-256");

            // 将验证码和盐值合并后，进行哈希计算
            byte[] hash = digest.digest((smsCode + salt).getBytes(StandardCharsets.UTF_8));

            // 将哈希值转换为十六进制字符串
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }

            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Failed to generate token", e);
        }
    }

    /**
     * 加密密码
     * @param password 未加密前密码
     * @return 加密后密码
     */
    public String encodePassword(String password) {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        return encoder.encode(password);
    }
}
